/*
 * Copyright 2013 JROSE
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jrose.test.runner;

import lombok.Getter;
import org.jrose.test.support.MongoTraceLoggerRedirector;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestContextManager;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;

/**
 * Abstract test runner implementation.
 *
 * @author Xiaohai Zhang
 * @since 2013-08-17 15:03
 */
abstract public class AbstractTestRunner extends SpringJUnit4ClassRunner {
    protected final Logger log = LoggerFactory.getLogger(getClass());

    @Getter private final ApplicationContext applicationContext;

    protected AbstractTestRunner(Class<?> clazz) throws InitializationError {
        super(clazz);
        this.applicationContext = initializeApplicationContext();
    }

    protected ApplicationContext initializeApplicationContext() {
        try {
            TestContextManager tcm = getTestContextManager();
            Field field = tcm.getClass().getDeclaredField("testContext");
            field.setAccessible(true);
            TestContext testContext = (TestContext) field.get(tcm);
            return testContext.getApplicationContext();
        } catch (Exception ex) {
            log.warn("Error occurs when trying to obtain applicationContext", ex);
            return null;
        }
    }

    final protected void runChild(final FrameworkMethod frameworkMethod, final RunNotifier notifier) {
        clearAllCachesIfNecessary();
        MongoTraceLoggerRedirector.init();

        Class<?> runtimeLevelClass;
        Class<?> currentRumtimeLevelClass;
        try {
            runtimeLevelClass = getClass().getClassLoader().loadClass("org.jrose.core.runtime.RuntimeLevel");
            currentRumtimeLevelClass = getClass().getClassLoader().loadClass("org.jrose.core.runtime.CurrentRuntimeLevel");
        } catch (ClassNotFoundException ex) {
            log.warn("No 'org.jrose.core.runtime.RuntimeLevel' loaded, ignore");
            super.runChild(frameworkMethod, notifier);
            return;
        }

        try {
            Object UNIT_TEST = runtimeLevelClass.getField("UNIT_TEST").get(null);
            Method method = currentRumtimeLevelClass.getMethod("setRuntimeLevel", runtimeLevelClass);
            method.invoke(null, UNIT_TEST);

            if (doRunChild(frameworkMethod, notifier)) {
                super.runChild(frameworkMethod, notifier);
            }
        } catch (Exception ex) {
            throw new RuntimeException(ex.getMessage(), ex);
        } finally {
            try {
                Method method = currentRumtimeLevelClass.getMethod("unsetRuntimeLevel");
                method.invoke(null);
            } catch (Exception ignored) {
                // ignored
            }
        }
    }

    abstract protected boolean doRunChild(FrameworkMethod frameworkMethod, RunNotifier notifier);

    private void clearAllCachesIfNecessary() {
        CacheManager cacheManager;
        try {
            cacheManager = getApplicationContext().getBean(CacheManager.class);
        } catch (BeansException ex) {
            // maybe no cache manager available, return
            return;
        }
        Collection<String> cacheNames = cacheManager.getCacheNames();
        if (cacheNames == null || cacheNames.isEmpty()) {
            return;
        }
        for (String cacheName : cacheNames) {
            Cache cache = cacheManager.getCache(cacheName);
            if (cache == null) {
                continue;
            }
            cache.clear();
        }
    }
}
